#include "AspectEngine.h"

namespace agtk {

SceneObject::SceneObject(char* name, char* path, bool shared)
{
	// Zero the scene object's translation and rotation.
	SetPosition( 0.0f, 0.0f, 0.0f );
	SetRotation( 0.0f, 0.0f, 0.0f );
	SetScale( 1.0f, 1.0f, 1.0f );

	// The object is initially at rest.
	SetVelocity( 0.0f, 0.0f, 0.0f );
	SetSpin( 0.0f, 0.0f, 0.0f );

	// The object is initially facing into the positive z-axis.
	SetForwardVector( 0.0f, 0.0f, 1.0f );
	SetRightVector( 1.0f, 0.0f, 0.0f );

	// Initially the object has no friction.
	SetFriction( 0.0f );

	// The object is visible, enabled, solid, and registering collisons by default.
	m_visible = true;
	m_enabled = true;
	m_ghost = false;
	m_ignoreGravity = false;
	m_ignoreCollisions = false;

	m_model = NULL;
	SetModel( name, path, shared );

	m_bOverridingDefaultMaterial = false;

	m_boundingSphere = new BoundingSphere;
	m_boundingSphere->radius = 5.0f;
}

SceneObject::SceneObject(StoreSceneObject t_store) {
	// Set the scene object's translation and rotation.
	SetPosition( t_store.position );
	SetRotation( t_store.rotation );
	SetScale( t_store.scale );

	// The object is initially at rest.
	SetVelocity( 0.0f, 0.0f, 0.0f );
	SetSpin( 0.0f, 0.0f, 0.0f );

	// The object is initially facing into the positive z-axis.
	SetForwardVector( 0.0f, 0.0f, 1.0f );
	SetRightVector( 1.0f, 0.0f, 0.0f );

	// Initially the object has no friction.
	SetFriction( 0.0f );

	// The object is visible, enabled, solid, and registering collisons by default.
	m_visible = t_store.visible;
	m_enabled = t_store.enabled;
	m_ghost = t_store.ghost;
	m_ignoreGravity = t_store.ignoreGravity;
	m_ignoreCollisions = t_store.ignoreCollisions;

	m_model = NULL;
	SetModel( const_cast<char*>(t_store.name.c_str()), const_cast<char*>(t_store.path.c_str()), t_store.sharedModel );

	m_bOverridingDefaultMaterial = t_store.overrideDefaultMaterial;
	if (m_bOverridingDefaultMaterial) {
		strcpy(m_materialName, t_store.materialName.c_str());
		strcpy(m_materialPath, t_store.materialPath.c_str());
		SetMaterial(new Material(m_materialName, m_materialPath));
	}

	m_boundingSphere = new BoundingSphere;
	m_boundingSphere->radius = 5.0f;
}

SceneObject::~SceneObject(void)
{
	// Destroy the object's mesh.
	if( m_sharedModel == true )
		g_aspectEngine->GetModelManager()->Remove( &m_model );
	else
		SAFE_DELETE( m_model );

	SAFE_DELETE(m_boundingSphere);
}

void SceneObject::Update(float gravity) {
	if (m_enabled) {
		float friction = 1.0f-m_friction;

		// Move the object.
		if (!m_ignoreGravity)
			AddVelocity(0.0f, gravity, 0.0f);

		m_velocity *= friction;
		Vec3<float> velocity = m_velocity*(float)g_aspectEngine->GetElapsedTicks();
		AddPosition( velocity );

		// Spin the object.
		m_spin *= friction;
		Vec3<float> spin = m_spin*(float)g_aspectEngine->GetElapsedTicks();
		AddRotation( spin );

		// Ensure rotation doesn't go over limit.
		if (m_rotation.Get(0) > 360.0f)
			AddRotation(-360.0f, 0.0f, 0.0f);
		if (m_rotation.Get(1) > 360.0f)
			AddRotation(0.0f, -360.0f, 0.0f);
		if (m_rotation.Get(2) > 360.0f)
			AddRotation(0.0f, 0.0f, -360.0f);
		if (m_rotation.Get(0) < -360.0f)
			AddRotation(360.0f, 0.0f, 0.0f);
		if (m_rotation.Get(1) < -360.0f)
			AddRotation(0.0f, 360.0f, 0.0f);
		if (m_rotation.Get(2) < -360.0f)
			AddRotation(0.0f, 0.0f, 360.0f);

		// Update the object's forward vector.
		m_forward.Set(0, (float)sin(m_rotation.Get(1)*PI/180));
		m_forward.Set(1, (float)-tan(m_rotation.Get(2)*PI/180));
		m_forward.Set(2, (float)cos(m_rotation.Get(1)*PI/180));
		m_forward = m_forward.Normalize();

		// Update the object's right vector.
		m_right.Set(0, (float)cos(m_rotation.Get(1)*PI/180));
		m_right.Set(1, (float)tan(m_rotation.Get(2)*PI/180));
		m_right.Set(2, (float)-sin(m_rotation.Get(1)*PI/180));
		m_right = m_right.Normalize();
	}
}

void SceneObject::Render() {
	if (m_visible) {
		glPushMatrix();
			glTranslatef(m_position.Get(0),
						 m_position.Get(1),
						 m_position.Get(2));
			glRotatef(m_rotation.Get(0), 1.0f, 0.0f, 0.0f);
			glRotatef(m_rotation.Get(1), 0.0f, 1.0f, 0.0f);
			glRotatef(m_rotation.Get(2), 0.0f, 0.0f, 1.0f);
			if (m_bOverridingDefaultMaterial)
				m_model->Render(m_material);
			else
				m_model->Render();
		glPopMatrix();
	}

	/*
	// Draw forward and right vectors. Useful for debugging, maybe.
	glBegin(GL_LINES);
	glColor3f(0.0f, 1.0f, 0.0f);
	glVertex3f(m_position.Get(0), m_position.Get(1), m_position.Get(2));
	glVertex3f(m_forward.Get(0), m_forward.Get(1), m_forward.Get(2));
	glColor3f(0.0f, 0.0f, 1.0f);
	glVertex3f(m_position.Get(0), m_position.Get(1), m_position.Get(2));
	glVertex3f(m_right.Get(0), m_right.Get(1), m_right.Get(2));
	glEnd();
	*/
}

void SceneObject::Drive( float force, bool lockYAxis ) {
	Vec3<float> realForce = m_forward;
	realForce *= force;

	m_velocity.Set(0, m_velocity.Get(0) + realForce.Get(0));
	m_velocity.Set(2, m_velocity.Get(2) + realForce.Get(2));

	if( lockYAxis == false )
		m_velocity.Set(1, m_velocity.Get(1) + realForce.Get(1));
}

void SceneObject::Strafe( float force, bool lockYAxis ) {
	Vec3<float> realForce = m_right;
	realForce *= force;

	m_velocity.Set(0, m_velocity.Get(0) + realForce.Get(0));
	m_velocity.Set(2, m_velocity.Get(2) + realForce.Get(2));

	if( lockYAxis == false )
		m_velocity.Set(1, m_velocity.Get(1) + realForce.Get(1));
}

void SceneObject::Stop() {
	m_velocity.Set(0.0f, 0.0f, 0.0f);
	m_spin.Set(0.0f, 0.0f, 0.0f);
}

void SceneObject::SetPosition( float x, float y, float z ) {
	m_position.Set(x,y,z);
}
void SceneObject::SetPosition( Vec3<float> position ) {
	m_position = position;
}
void SceneObject::AddPosition( float x, float y, float z ) {
	m_position.Set(m_position.Get(0)+x,
				   m_position.Get(1)+y,
				   m_position.Get(2)+z);
}
void SceneObject::AddPosition( Vec3<float> position ) {
	m_position += position;
}
Vec3<float> SceneObject::GetPosition() {
	return m_position;
}

void SceneObject::SetRotation( float x, float y, float z ) {
	m_rotation.Set(x,y,z);
}
void SceneObject::SetRotation( Vec3<float> rotation ) {
	m_rotation = rotation;
}
void SceneObject::AddRotation( float x, float y, float z ) {
	m_rotation.Set(m_rotation.Get(0)+x, m_rotation.Get(1)+y, m_rotation.Get(2)+z);
}
void SceneObject::AddRotation( Vec3<float> rotation ) {
	m_rotation += rotation;
}
Vec3<float> SceneObject::GetRotation() {
	return m_rotation;
}

void SceneObject::SetScale( float x, float y, float z ) {
	m_scale.Set(x,y,z);
}
void SceneObject::SetScale( Vec3<float> scale ) {
	m_scale = scale;
}
void SceneObject::AddScale( float x, float y, float z ) {
	m_scale.Set(m_scale.Get(0)+x,
				m_scale.Get(1)+y,
				m_scale.Get(2)+z);
}
void SceneObject::AddScale( Vec3<float> scale ) {
	m_scale += scale;
}
Vec3<float> SceneObject::GetScale() {
	return m_scale;
}

void SceneObject::SetVelocity( float x, float y, float z ) {
	m_velocity.Set(x,y,z);
}
void SceneObject::SetVelocity( Vec3<float> velocity ) {
	m_velocity = velocity;
}
void SceneObject::AddVelocity( float x, float y, float z ) {
	m_velocity.Set(m_velocity.Get(0)+x,
				   m_velocity.Get(1)+y,
				   m_velocity.Get(2)+z);
}
void SceneObject::AddVelocity( Vec3<float> velocity ) {
	m_velocity += velocity;
}
Vec3<float> SceneObject::GetVelocity() {
	return m_velocity;
}

void SceneObject::SetSpin( float x, float y, float z ) {
	m_spin.Set(x,y,z);
}
void SceneObject::SetSpin( Vec3<float> spin ) {
	m_spin = spin;
}
void SceneObject::AddSpin( float x, float y, float z ) {
	m_spin.Set(m_spin.Get(0)+x,
			   m_spin.Get(1)+y,
			   m_spin.Get(2)+z);
}
void SceneObject::AddSpin( Vec3<float> spin ) {
	m_spin += spin;
}
Vec3<float> SceneObject::GetSpin() {
	return m_spin;
}

void SceneObject::SetForwardVector(float x, float y, float z) {
	Vec3<float> forward(x,y,z);
	m_forward=forward;
}
void SceneObject::SetForwardVector(Vec3<float> vector) {
	m_forward=vector;
}
void SceneObject::SetRightVector(float x, float y, float z) {
	Vec3<float> right(x,y,z);
	m_right=right;
}
void SceneObject::SetRightVector(Vec3<float> vector) {
	m_right=vector;
}
Vec3<float> SceneObject::GetForwardVector() {
	return m_forward;
}
Vec3<float> SceneObject::GetRightVector() {
	return m_right;
}

void SceneObject::SetFriction( float friction ) {
	m_friction=friction;
}

void SceneObject::SetVisible( bool visible ) {
	m_visible=visible;
}
bool SceneObject::GetVisible() {
	return m_visible;
}

void SceneObject::SetEnabled( bool enabled ) {
	m_enabled = enabled;
}
bool SceneObject::GetEnabled() {
	return m_enabled;
}

void SceneObject::SetGhost( bool ghost ) {
	m_ghost = ghost;
}
bool SceneObject::GetGhost() {
	return m_ghost;
}

void SceneObject::SetIgnoreGravity( bool ignoreGravity ) {
	m_ignoreGravity = ignoreGravity;
}
bool SceneObject::GetIgnoreGravity() {
	return m_ignoreGravity;
}

void SceneObject::SetIgnoreCollisions( bool ignoreCollisions ) {
	m_ignoreCollisions = ignoreCollisions;
}
bool SceneObject::GetIgnoreCollisions() {
	return m_ignoreCollisions;
}

void SceneObject::SetModel( char *name, char *path, bool shared ) {
	// Destroy the object's existing mesh.
	if( m_sharedModel == true )
		g_aspectEngine->GetModelManager()->Remove( &m_model );
	else
		SAFE_DELETE( m_model );

	// Indicate if the object is sharing this mesh.
	m_sharedModel = shared;

	// Ensure a mesh was specified.
	if( name != NULL && path != NULL )
	{
		// Load the object's mesh.
		if( m_sharedModel == true )
			m_model = g_aspectEngine->GetModelManager()->Add( name, path );
		else
			m_model = new Model( name, path );
	}
}

Model* SceneObject::GetModel(void) {
	return m_model;
}

void SceneObject::SetMaterial(Material *material) {
	// If there is already an overriding material, remove it first.
	if (m_bOverridingDefaultMaterial) {
		g_aspectEngine->GetMaterialManager()->Remove(&m_material);
	}

	// Set m_material tp point to the given material.
	m_material = material;

	// Set file name and path into object.
	strcpy(m_materialName, material->GetName());
	strcpy(m_materialPath, material->GetPath());

	// Set switch to true no matter what.
	m_bOverridingDefaultMaterial = true;
}

void SceneObject::SetDefaultMaterial() {
	// If there is already an overriding material, remove it and revert.
	if (m_bOverridingDefaultMaterial) {
		g_aspectEngine->GetMaterialManager()->Remove(&m_material);
		m_material = m_model->GetMaterial();
	}
	// Else, we are already using the default material.
}

Material* SceneObject::GetMaterial() {
	return m_material;
}

StoreSceneObject SceneObject::GetDataStore(void) {
	StoreSceneObject store;

	store.name = m_model->GetName();
	store.path = m_model->GetPath();
	if (m_bOverridingDefaultMaterial) {
		store.materialName = m_materialName;
		store.materialPath = m_materialPath;
		store.overrideDefaultMaterial = true;
	}
	store.position = m_position;
	store.rotation = m_rotation;
	store.scale = m_scale;
	store.visible = m_visible;
	store.enabled = m_enabled;
	store.ghost = m_ghost;
	store.ignoreGravity = m_ignoreGravity;
	store.ignoreCollisions = m_ignoreCollisions;
	store.sharedModel = m_sharedModel;

	return store;
}

}